package archer.framework.utils;


import archer.framework.client.config.RemoteInvokeConfig;
import archer.framework.client.config.RemoteInvokeConfigFactory;
import archer.framework.client.exception.RemoteInvokeException;
import archer.framework.client.result.ListExecuteResult;
import archer.framework.client.result.MapExecuteResult;
import archer.framework.protocol.ArcherProtocolModule;
import archer.framework.protocol.result.ExecuteResult;
import archer.framework.protocol.result.ResultCode;
import archer.framework.web.entity.AccessLog;
import jodd.http.HttpBrowser;
import jodd.http.HttpException;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import jodd.servlet.ServletUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.sql.Timestamp;
import java.util.Enumeration;
import java.util.Map;

/**
 * 接口调用工具类
 *
 * @author christ
 * @date 2015/8/11
 */
public class RemoteInvokeUtils {

    private static Logger logger = LoggerFactory.getLogger(RemoteInvokeUtils.class);

    /**
     * 接口调用出错处理
     *
     * @param e
     * @return
     */
    public static ExecuteResult fail(Exception e) {

        logger.error(e.getMessage(), e);

        return new ExecuteResult()
                .setResultCode(ResultCode.INTERNAL_ERROR.name())
                .setResultMsg("系统升级中，请稍后再试");
    }

    /**
     * get请求
     *
     * @param req
     * @param resp
     * @param url
     * @return
     */
    public static ExecuteResult get(HttpServletRequest req, HttpServletResponse resp, String url) {

        try {
            return get(req, resp, url, null);
        } catch (Exception e) {
            return fail(e);
        }

    }

    public static ExecuteResult get(String url) {
        try {
            return get(null, null, url, null);
        } catch (Exception e) {
            return fail(e);
        }
    }


    /**
     * get请求
     *
     * @param req
     * @param resp
     * @param url
     * @param queryMap
     * @return
     */
    public static ExecuteResult get(HttpServletRequest req, HttpServletResponse resp, String url, Map<String, Object> queryMap) {

        try {
            return deserialize(invoke(req, resp, "GET", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }

    public static ExecuteResult get(String url, Map<String, Object> queryMap) {
        try {
            return deserialize(invoke(null, null, "GET", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }


    /**
     * post请求
     *
     * @param req
     * @param resp
     * @param url
     * @return
     */
    public static ExecuteResult post(HttpServletRequest req, HttpServletResponse resp, String url) {
        try {
            return post(req, resp, url, null);
        } catch (Exception e) {
            return fail(e);
        }
    }

    public static ExecuteResult post(String url) {
        try {
            return post(null, null, url, null);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * post请求
     *
     * @param req
     * @param resp
     * @param url
     * @param queryMap
     * @return
     */
    public static ExecuteResult post(HttpServletRequest req, HttpServletResponse resp, String url, Map<String, Object> queryMap) {
        try {
            return deserialize(invoke(req, resp, "POST", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }

    public static ExecuteResult post(String url, Map<String, Object> queryMap) {
        try {
            return deserialize(invoke(null, null, "POST", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * put请求
     *
     * @param req
     * @param resp
     * @param url
     * @return
     */
    public static ExecuteResult put(HttpServletRequest req, HttpServletResponse resp, String url) {
        try {
            return put(req, resp, url, null);
        } catch (Exception e) {
            return fail(e);
        }
    }

    public static ExecuteResult put(String url) {
        try {
            return put(null, null, url, null);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * put请求
     *
     * @param req
     * @param resp
     * @param url
     * @param queryMap
     * @return
     */
    public static ExecuteResult put(HttpServletRequest req, HttpServletResponse resp, String url, Map<String, Object> queryMap) {
        try {
            return deserialize(invoke(req, resp, "PUT", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }

    public static ExecuteResult put(String url, Map<String, Object> queryMap) {
        try {
            return deserialize(invoke(null, null, "PUT", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * delete请求
     *
     * @param req
     * @param resp
     * @param url
     * @return
     */
    public static ExecuteResult delete(HttpServletRequest req, HttpServletResponse resp, String url) {
        try {
            return delete(req, resp, url, null);
        } catch (Exception e) {
            return fail(e);
        }
    }

    public static ExecuteResult delete(String url) {
        try {
            return delete(null, null, url, null);
        } catch (Exception e) {
            return fail(e);
        }
    }

    /**
     * delete请求
     *
     * @param req
     * @param resp
     * @param url
     * @param queryMap
     * @return
     */
    public static ExecuteResult delete(HttpServletRequest req, HttpServletResponse resp, String url, Map<String, Object> queryMap) {
        try {
            return deserialize(invoke(req, resp, "DELETE", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }

    public static ExecuteResult delete(String url, Map<String, Object> queryMap) {
        try {
            return deserialize(invoke(null, null, "DELETE", url, queryMap));
        } catch (Exception e) {
            return fail(e);
        }
    }


    //-------------------------------------------------------helper-------------------------------------------------------------------

    public static void log(HttpServletRequest request) {

        AccessLog log = new AccessLog()
                .setName("REMOTE API CALL")
                .setUrl(ServletUtils.getRequestURI(request))
                .setParams(RequestParamUtils.getQueryStringWithMask(request.getParameterMap(), "password"))
                .setMethod(ServletUtils.getMethod(request))
                .setUserAgent(ServletUtils.getRequest(request).getHeader("User-Agent"))
                .setAccessDate(new Timestamp(System.currentTimeMillis()))
                .setIp(ServletUtils.getClientIp(request));

        logger.info(log.toLogStr());
    }

    /**
     * 接口调用
     *
     * @param req
     * @param resp
     * @param url
     * @param queryMap
     * @return
     */
    private static String invoke(HttpServletRequest req, HttpServletResponse resp, String method, String url, Map<String, Object> queryMap) {

        log(req);

        HttpResponse httpResponse = innerInvoke(req, resp, method, url, queryMap);

        int statusCode = httpResponse.statusCode();


        // 为结果设置返回状态
        if (resp != null) {
            resp.setStatus(statusCode);
        }

        // 如果调用成功则返回远程调用结果
        if (200 == statusCode) {
            return httpResponse.bodyText();
        }

        // 调用出错则抛错
        throw new RemoteInvokeException();
    }

    /**
     * @param req
     * @param resp
     * @param method
     * @param url
     * @param queryMap
     * @return
     * @throws RemoteInvokeException
     */
    private static HttpResponse innerInvoke(HttpServletRequest req, HttpServletResponse resp, String method, String url, Map queryMap) throws RemoteInvokeException {

        HttpRequest request = new HttpRequest().method(method).set(decodeUrl(getConfig(url).getHost() + url, getConfig(url).getMask(), getConfig(url).getVersion())).charset("utf-8");

        request.header("User-Agent", ArcherProtocolModule.userAgent);

        // 设置连接的timeout属性
        request.timeout(getConfig(url).getTimeout());

        // 添加接口调用参数，接口调用参数的优先级高于request中的参数，这样可以防止前端注入一些不该注入的东西
        if (queryMap != null) {
            if ("POST".equals(method)) {
                request.form(queryMap);
            } else {
                request.query(queryMap);
            }
        }

        // 为接口调用请求添加apikey参数
        addApikey(request, url);

        // 为接口调用请求添加token
        if (req != null) {
            addToken(request, req.getCookies());
        }

        // 为接口调用请求添加request中的参数
        if (req != null) {
            addParam(request, req, queryMap);
        }

        // 不管远程服务端是否需要签名验证，都进行签名操作，没影响
        if ("POST".equals(method)) {
            String sign = SignatureUtils.genSignature(HttpUtils.asParameterMap(request.form()), getConfig(url).getApisecret(), ArcherProtocolModule.signatureField);
            if (ValidateUtils.isNotEmpty(sign)) {
                request.form(ArcherProtocolModule.signatureField, sign);
            }
        } else {
            String sign = SignatureUtils.genSignature(HttpUtils.asParameterMap(request.query()), getConfig(url).getApisecret(), ArcherProtocolModule.signatureField);
            if (ValidateUtils.isNotEmpty(sign)) {
                request.query(ArcherProtocolModule.signatureField, sign);
            }
        }

        HttpBrowser browser = new HttpBrowser();

        try {
            // 发起请求
            HttpResponse httpResponse = browser.sendRequest(request);


            if (200 == httpResponse.statusCode()) {

                // 如果请求的是登录接口，则将接口返回的token写入cookie
                if (needCookieToken(url)) {
                    if (resp != null) {
                        cookieToken(httpResponse.bodyText(), resp);
                    }
                }
                return httpResponse;
            }

            // 如果返回的code不是200，则抛错
            throw new RemoteInvokeException("调用接口：" + url + "返回的response-code:" + httpResponse.statusCode());

        } catch (Exception e) {

            // 记录错误日志
            RemoteInvokeConfig config = getConfig(url);
            StringBuilder sb = new StringBuilder();
            sb.append("接口调用出错 host=").append(config.getHost()).append(" url=").append(url);
            logger.error(sb.toString(), e);


            //-----------------------------------
            // 如果是connection类的问题则尝试重连
            if (e instanceof ConnectException || e instanceof HttpException) {

                config.setRetry(config.getRetry() + 1);

                if (config.getRetry() < config.getHosts().size()) {
                    config.setHost(config.getHosts().get(config.getRetry()));

                    sb = new StringBuilder();
                    sb.append("尝试第").append(config.getRetry()).append("次重连，重连服务器").append(config.getHost());
                    logger.error(sb.toString(), e);

                    return innerInvoke(req, resp, method, url, queryMap);
                }

                // 如果所有的服务器都已经重试则重置config，以便下一次请求可以重新遍历所有服务器
                resetConfig(config);
            }

            //-----------------------------------
            // 非connection类问题或者所有的服务器都已经测试重连，直接抛错
            // 此处重新抛出一个不带详细信息的Exception
            throw new RemoteInvokeException("接口调用出错");
        }
    }

    /**
     * 重置连接配置
     *
     * @param config
     */
    private static void resetConfig(RemoteInvokeConfig config) {
        config.setRetry(0);
        config.setHost(config.getHosts().get(config.getRetry()));
    }

    /**
     * 获得服务配置信息
     *
     * @return
     */
    public static RemoteInvokeConfig getConfig(String url) {

        if (url.startsWith("/")) {
            return RemoteInvokeConfigFactory.getConfig(url.split("/")[1]);
        }

        return RemoteInvokeConfigFactory.getConfig(url.split("/")[0]);
    }


    /**
     * 解码mask
     *
     * @param url
     * @return
     */
    public static String decodeUrl(String url, String mask, String version) {
        return url.replace(mask, "api/" + version);
    }

    /**
     * 格式化请求
     *
     * @param req
     * @param resp
     */
    public static void format(HttpServletRequest req, HttpServletResponse resp) {
        resp.setContentType("application/json");
        resp.setCharacterEncoding("UTF-8");
        try {
            req.setCharacterEncoding("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获得访问接口服务器的url
     *
     * @param req
     * @return
     */
    public static String getUrl(HttpServletRequest req) {
        String url = req.getRequestURI().substring(req.getContextPath().length());
        return getConfig(url).getHost() + url;
    }

    /**
     * 为接口调用请求添加apiKey参数
     *
     * @param httpRequest
     */
    public static void addApikey(HttpRequest httpRequest, String url) {

        if ("POST".equals(httpRequest.method())) {

            if (httpRequest.form() == null || (httpRequest.form() != null && !httpRequest.form().contains(ArcherProtocolModule.apikeyField))) {
                httpRequest.form(ArcherProtocolModule.apikeyField, getConfig(url).getApikey());
            }

        } else {

            if (httpRequest.query() == null || (httpRequest.query() != null && !httpRequest.query().contains(ArcherProtocolModule.apikeyField))) {
                httpRequest.query(ArcherProtocolModule.apikeyField, getConfig(url).getApikey());
            }
        }
    }

    /**
     * 为接口调用请求添加界面参数
     *
     * @param request
     * @param req
     */
    public static void addParam(HttpRequest request, HttpServletRequest req, Map<String, Object> queryMap) {
        Enumeration enumeration = req.getParameterNames();
        while (enumeration.hasMoreElements()) {
            String name = (String) enumeration.nextElement();

            // 如果是apikey和signature参数则不信赖来自界面调用传递来的参数
            if (ArcherProtocolModule.apikeyField.equals(name) || ArcherProtocolModule.signatureField.equals(name)) {
                continue;
            }

            if ("POST".equals(request.method())) {
                if (queryMap == null || !queryMap.containsKey(name)) {
                    request.form(name, ServletUtil.value(req, name));
                }
            } else {
                if (queryMap == null || !queryMap.containsKey(name)) {
                    request.query(name, ServletUtil.value(req, name));
                }
            }

        }
    }

    /**
     * 将服务器返回的token存储到cookie中
     *
     * @param json
     * @param resp
     */
    public static void cookieToken(String json, HttpServletResponse resp) {
        ExecuteResult result = deserialize(json);
        if (result.isSuccess()) {
            if (result.getResultData() instanceof Map) {
                Map map = (Map) result.getResultData();
                resp.addCookie(new Cookie(ArcherProtocolModule.tokenField, (String) map.get(ArcherProtocolModule.tokenField)));
            }
        }
    }

    /**
     * 是否需要将token存储到cookie中
     *
     * @param url
     * @return
     */
    public static boolean needCookieToken(String url) {
        String loginUrl = getConfig(url).getLoginUrl();
        if (ValidateUtils.isNotEmpty(loginUrl)) {
            String[] urls = loginUrl.split(",");

            for (String each : urls) {
                if (url.endsWith(each)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 为接口调用请求添加token
     *
     * @param httpRequest
     * @param cookies
     */
    public static void addToken(HttpRequest httpRequest, Cookie[] cookies) {

        StringBuilder cookieString = new StringBuilder();
        boolean first = true;

        if (cookies != null) {
            for (Cookie cookie : cookies) {

                Integer maxAge = cookie.getMaxAge();
                if (maxAge != null && maxAge.intValue() == 0) {
                    continue;
                }

                if (!first) {
                    cookieString.append("; ");
                }
                first = false;
                cookieString.append(cookie.getName());
                cookieString.append('=');
                cookieString.append(cookie.getValue());

                if ("POST".equals(httpRequest.method())) {
                    // 添加token到请求参数中
                    if (ArcherProtocolModule.tokenField.equals(cookie.getName())) {
                        httpRequest.form(ArcherProtocolModule.tokenField, cookie.getValue());
                    }
                } else {
                    // 添加token到请求参数中
                    if (ArcherProtocolModule.tokenField.equals(cookie.getName())) {
                        httpRequest.query(ArcherProtocolModule.tokenField, cookie.getValue());
                    }
                }

            }

            httpRequest.header("cookie", cookieString.toString(), true);
        }
    }


    /**
     * json转对象
     *
     * @param json
     * @return
     */
    public static ExecuteResult deserialize(String json) {

        try {
            MapExecuteResult result = JSONUtils.deserialize(json, MapExecuteResult.class);
            if (ValidateUtils.isNotEmpty(result)) {
                return result.cast();
            }
        } catch (Exception e) {
            ListExecuteResult result = JSONUtils.deserialize(json, ListExecuteResult.class);
            if (ValidateUtils.isNotEmpty(result)) {
                return result.cast();
            }
        }

        throw new RemoteInvokeException();
    }
}
